package org.jeecg.config.sign.filters;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

//https://www.modb.pro/db/79697

/**
 * 防止SQL注入过滤器
 *
 * @author Slam
 */
@Slf4j
@Component
@WebFilter(urlPatterns = "/*")
public class SqlInjectionFilter implements Filter {

    /**
     *  特殊字符  "=","&",
    */
    //https://www.digitalocean.com/community/tutorials/sql-injection-in-java
    private static final String[] SQL_INJECTION_CHARACTERS =
            {"'", "’","\"", ";", "#", "--", "*", "/*", "*/", "%", "_",
                    "(", ")", "+", "-", "|", "<", ">", "!", "^"};
    /**
     * 全匹配关键字
     */
    private static final String[] FULL_MATCH_INJECTION_KEYWORDS =
            {"create", "or", "by", "use", "if",
                    "and", "union", "from", "left", "right", "ord",
                    "update",};
    // 共享过滤字符
    private static final String[] SHARED_INJECTION_KEYWORDS =
            {"table",  "database", "concat", "exec", "execute",
                    "information_schema", "table_schema", "chr",
                    "mid", "master", "truncate", "sitename",
                    "grant", "group_concat", "where", "like",
                    "count", "declare", "column_name", "substr",
                    "substring", "substring_index", "rlike", "rlike",
                    "ascii", "regexp"};
    //  基于错误的注入
    private static final String[] ERROR_BASED_INJECTION_KEYWORDS =
            {"order by", "group by", "having", "limit", "join", "not"};
    // 基于时间的注入
    private static final String[] TIME_BASED_INJECTION_KEYWORDS =
            {"sleep", "benchmark", "waitfor", "pg_sleep", "delay"};
    // 基于时间盲注注入
    private static final String[] TIME_BASED_BLIND_INJECTION_KEYWORDS =
            {"sleep", "benchmark", "waitfor", "pg_sleep"};
    // 基于布尔盲注注入
    private static final String[] BOOL_BASED_BLIND_INJECTION_KEYWORDS =
            {"not", "xor", "case"};
    /**
     * 基于错误盲注注入
     */
    private static final String[] ERROR_BLIND_BASED_INJECTION_KEYWORDS =
            {"floor", "extractvalue", "updatexml", "exp", "geometrycollection",
                    "polygon", "multipoint", "multlinestring", "multpolygon"};
    // 堆叠查询注入
    private static final String[] STACKED_QUERY_INJECTION_KEYWORDS =
            {"select", "insert", "delete", "drop",};
    // 基于联合查询的注入
    private static final String[] UNION_QUERY_INJECTION_KEYWORDS =
            {"truncate", "alter"};
    // 基于报错的注入
    private static final String[] ERROR_MESSAGE_BASED_INJECTION_KEYWORDS =
            {"exec", "xp_cmdshell", "@@version", "@version", "sys_user"};
    private Pattern sqlPattern;

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain)
            throws IOException, ServletException {
        boolean hasSqlInjection = false;
//        log.info("\n\n------------------Access SqlInjectionFilter doFilter -----------------\n\n");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 获取所有参数名和值，并过滤SQL注入攻击
        for (String paramName : request.getParameterMap().keySet()) {
            String[] paramValues = request.getParameterValues(paramName);
            for (int i = 0; i < paramValues.length; i++) {
                String filteredValue = filterSQL(paramValues[i]);
                paramValues[i] = filteredValue;
            }
        }

        // 获取所有请求参数
        Map<String, String[]> parameterMap = new HashMap<>(request.getParameterMap());

        // 验证关键字
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String parameterName = entry.getKey();
            String[] parameterValues = entry.getValue();

            hasSqlInjection = detectSqlInjectionFullMatch(parameterName, FULL_MATCH_INJECTION_KEYWORDS);
            if (hasSqlInjection) {
                break;
            }

            // 验证和编码参数值
            for (String value : parameterValues) {
                hasSqlInjection = detectSqlInjectionFullMatch(value, FULL_MATCH_INJECTION_KEYWORDS);
                if (hasSqlInjection) {
                    break;
                }
            }
        }

        if (hasSqlInjection) {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.write("您所访问的页面请求中有违反安全规则元素存在，拒绝访问!");
            log.info("您所访问的页面请求中有违反安全规则元素存在，拒绝访问!");
            return;
        }

        String[][] nestedArray = {
                SHARED_INJECTION_KEYWORDS,
                ERROR_BASED_INJECTION_KEYWORDS,
                TIME_BASED_INJECTION_KEYWORDS,
                TIME_BASED_BLIND_INJECTION_KEYWORDS,
                BOOL_BASED_BLIND_INJECTION_KEYWORDS,
                ERROR_BLIND_BASED_INJECTION_KEYWORDS,
                STACKED_QUERY_INJECTION_KEYWORDS,
                UNION_QUERY_INJECTION_KEYWORDS,
                ERROR_MESSAGE_BASED_INJECTION_KEYWORDS
        };
        String[] allKeywords = Arrays.stream(nestedArray)
                .flatMap(Arrays::stream)
                .toArray(String[]::new);


        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String parameterName = entry.getKey();
            String[] parameterValues = entry.getValue();

            hasSqlInjection = detectSqlInjectionContainsMatch(parameterName, allKeywords);
            if (hasSqlInjection) {
                break;
            }

            // 验证和编码参数值
            for (String value : parameterValues) {
                hasSqlInjection = detectSqlInjectionContainsMatch(value, allKeywords);
                if (hasSqlInjection) {
                    break;
                }
            }
        }

//        if (hasSqlInjection) {
//            response.setCharacterEncoding("UTF-8");
//            response.setContentType("application/json;charset=UTF-8");
//            PrintWriter out = response.getWriter();
//            out.write("您所访问的页面请求中有违反安全规则元素存在，拒绝访问!");
//            log.info("您所访问的页面请求中有违反安全规则元素存在，拒绝访问!");
//            return;
//        }

        //  过滤参数特殊字符
//        filterChain.doFilter(new SqlInjectionRequestWrapper(request, SQL_INJECTION_CHARACTERS),
//                response);


        // 验证敏感参数和编码所有的参数
//        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//            String parameterName = entry.getKey();
//            String[] parameterValues = entry.getValue();
//
//            // 验证和编码参数值
//            for (int i = 0; i < parameterValues.length; i++) {
//                String encodedValue = validateAndEncode(parameterValues[i]);
//                parameterValues[i] = encodedValue;
//            }
//            httpRequest.setAttribute(parameterName, parameterValues);
//        }


//        HttpServletResponse combineResponse = (HttpServletResponse) response;
//        combineResponse.addHeader("Access-Control-Allow-Origin", "*");
//        combineResponse.addHeader("X-XSS-Protection", "1; mode=block");
//        combineResponse.addHeader("X-Content-Type-Options", "nosniff");
//        combineResponse.addHeader("X-Frame-Options", "DENY");
//        combineResponse.addHeader("Set-Cookie", "HttpOnly");
//        combineResponse.addHeader("Set-Cookie", "Secure");
//        combineResponse.addHeader("Strict-Transport-Security", "max-age=63072000; includeSubdomains");
//        combineResponse.addHeader("Referrer-Policy", "origin");
//        combineResponse.addHeader("X-Permitted-Cross-Domain-Policies", "master-only");
//        combineResponse.addHeader("X-Download-Options", "noopen");
//        combineResponse.addHeader("Content-Security-Policy", "upgrade-insecure-requests;content *;img-src * data:");


//        filterChain.doFilter(request, combineResponse);
        filterChain.doFilter(request, response);
    }

    // 过滤SQL字符串中的特殊字符和关键字
    private String filterSQL(String str) {
        if (str == null) {
            return null;
        }
        for (String injectionChar : SQL_INJECTION_CHARACTERS) {
//            log.info("\n\n------------before str = '{}' injectionChar = '{}'  ---------------\n\n", str, injectionChar);
            str = str.replace(injectionChar, "");
//            log.info("\n\n------------after str = '{}' injectionChar = '{}'  ---------------\n\n", str, injectionChar);
        }

        return str;
    }

    private boolean detectSqlInjectionFullMatch(String value, String[] keywords) {
        if (value != null) {
            for (String keyword : keywords) {
//                log.info("\n\n----------------value = {} keyword = {} ------------------------\n\n",
//                        value, keyword);

                String[] subValues = value.split(" ");
                for (String subValue: subValues) {
                    if (subValue.equalsIgnoreCase(keyword.toLowerCase())) {
                        log.info("\n\n----------------subValue = '{}' keyword = '{}' ------------------------\n\n",
                                subValue, keyword);
                        return true;
                    }
                }

            }
        }

        return false;
    }

    private boolean detectSqlInjectionContainsMatch(String value, String[] keywords) {
        if (value != null) {
            for (String keyword : keywords) {
                if (value.toLowerCase().contains(keyword.toLowerCase())) {
                    return true;
                }
            }
        }

        return false;
    }


    // 验证和编码参数值
    private String validateAndEncode(String parameterValue) {
        // TODO: 在此处添加适当的验证和编码逻辑

        // 验证用户名和密码只包含数字和字母
//        if (!parameterValue.matches("[a-zA-Z0-9]+")) {
//            throw new IllegalArgumentException("用户名或密码包含非法字符！");
//        }
// 编码特殊字符
//        return URLEncoder.encode(parameterValue, "UTF-8");
        // 验证邮件地址格式是否正确
//        if (!parameterValue.matches("^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$")) {
//            throw new IllegalArgumentException("邮箱地址格式不正确！");
//        }
//        // 编码特殊字符
//        return URLEncoder.encode(parameterValue, "UTF-8");

        // 验证手机号码格式是否正确
//        if (!parameterValue.matches("^1[3456789]\\d{9}$")) {
//            throw new IllegalArgumentException("手机号码格式不正确！");
//        }
//        // 编码特殊字符
//        return URLEncoder.encode(parameterValue, "UTF-8");

        return parameterValue;
    }


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //初始化操作
    }


    @Override
    public void destroy() {
        //销毁操作
    }

}

//https://juejin.cn/post/7011005750143090695
//   # 配置位置如下
//  proxy_cookie_path / "/; httponly; secure; SameSite=Lax";
//        add_header  X-XSS-Protection  "1; mode=block";
//        add_header  X-Content-Type-Options  nosniff;
//    #（1）DENY：不能被嵌入到任何iframe或frame中。
//    #（2）SAMEORIGIN：页面只能被本站页面嵌入到iframe或者frame中。
//    #（3）ALLOW-FROM uri：只能被嵌入到指定域名的框架中。
//        add_header  X-Frame-Options  DENY;
//        add_header  Set-Cookie  HttpOnly;
//        add_header  Set-Cookie  Secure;
//        add_header  Strict-Transport-Security  "max-age=63072000; includeSubdomains"  always;
//        add_header  Referrer-Policy  value;
//        add_header  X-Permitted-Cross-Domain-Policies  value;
//        add_header  X-Download-Options  value;
//         add_header  Content-Security-Policy  "upgrade-insecure-requests;content *;img-src '*' data: '*'";


//https://www.cnblogs.com/hjylxh/p/13323313.html
//        Cookie[] cookies = ((HttpServletRequest) httpRequest).getCookies();
//        StringBuilder builder = new StringBuilder();


//    基于错误的注入：攻击者利用应用程序的错误消息来获取有关数据库结构和数据的信息。攻击者利用这些信息来发动更高级的攻击。
//      "'" （单引号）：用于在SQL语句中引用字符串值的字符，攻击者可以使用单引号来绕过输入验证。
//      "" （反斜杠）：用于在SQL语句中转义特殊字符的字符，攻击者可以使用反斜杠来绕过输入验证。
//      ";" （分号）：可以用于分隔多条SQL语句。
//      "--" （双短横线）：SQL注释符号，可以用于注释掉SQL语句中的一部分。
//      "/" 和 "/" （斜杠星号和星号斜杠）：SQL注释符号，可以用于注释掉SQL语句中的一部分。


//    基于时间的注入：攻击者利用应用程序在响应SQL查询时的延迟来获取有关数据库结构和数据的信息。攻击者利用这些信息来发动更高级的攻击。
//      "sleep" 或 "benchmark"：攻击者可以使用这些SQL函数来延长SQL查询的执行时间。
//      "waitfor"：攻击者可以使用此语句来等待指定的时间。
//      "pg_sleep"：PostgreSQL数据库特定的函数，用于延长查询执行时间

//    盲注注入：攻击者利用应用程序的响应来判断SQL查询的结果，而不是直接获取查询结果。攻击者利用这些信息来发动更高级的攻击。
//"--" 或 "#"：注释符号，用于注释SQL查询中的文本。攻击者可以在注释符号之后添加其他文本来修改查询的行为。
//"union"：用于将结果集合并在一起的SQL关键字。攻击者可以使用它来访问其他表或获取其他敏感信息。
//"select"：用于选择数据的SQL关键字。攻击者可以使用它来获取敏感数据或其他信息。
//"from"：用于指定查询表的SQL关键字。攻击者可以使用它来访问其他表或获取其他敏感信息。
//"where"：用于指定查询条件的SQL关键字。攻击者可以使用它来更改查询条件或添加其他查询条件。
//"and" 或 "or"：用于连接查询条件的SQL关键字。攻击者可以使用它们来添加其他查询条件。
//"sleep" 或 "benchmark"：攻击者可以使用这些SQL函数来延长SQL查询的执行时间。


//    堆叠查询注入：攻击者利用多个SQL查询的结果来构造一个完整的SQL查询。这种注入方式通常用于绕过应用程序的过滤器。
//        ";" （分号）：堆叠查询注入中常用的分隔符，可以用来分隔多条SQL语句。
//        "--" （双短横线）：SQL注释符号，可以用来注释掉SQL语句中的一部分。
//        "/" 和 "/" （斜杠星号和星号斜杠）：SQL注释符号，可以用来注释掉SQL语句中的一部分。
//        "union"：用于合并两个或多个SELECT语句的关键字，可以用于执行联合查询。
//        "select"：用于从数据库中获取数据的关键字。
//        "insert"：用于向数据库中插入数据的关键字。
//        "update"：用于更新数据库中的数据的关键字。
//        "delete"：用于从数据库中删除数据的关键字。
//        "drop"：用于删除数据库表的关键字。
//    基于联合查询的注入：攻击者利用联合查询来获取数据库中的敏感数据。攻击者通过构造联合查询，将两个或更多的SQL查询合并为一个结果集。
//禁用 "union select": 这是最常见的联合查询注入攻击方法，因此禁用这个关键字可以有效地防止注入攻击。
//
//禁用 "drop table" 和 "truncate table": 这些语句会删除表格和数据，可能会导致数据丢失和服务不可用。
//
//禁用 "update" 和 "delete": 这些语句会更改和删除数据，可能会导致数据丢失和服务不可用。
//
//禁用 "create" 和 "alter": 这些语句会更改数据库结构，可能会导致数据丢失和服务不可用。
//
//禁用所有的注释符号：注释符号（例如"--"和"/* */"）可以用于在SQL查询中插入恶意代码。
//    基于报错的注入：攻击者利用应用程序在执行SQL查询时生成的错误消息来获取有关数据库结构和数据的信息。攻击者利用这些信息来发动更高级的攻击。
//      禁用 "select": 在错误消息中可能包含有关查询结果的信息，攻击者可以利用这些信息来进一步攻击系统。
//        禁用 "update" 和 "delete": 在错误消息中可能包含有关修改或删除操作的信息，攻击者可以利用这些信息来进一步攻击系统。
//        禁用 "insert": 在错误消息中可能包含有关插入操作的信息，攻击者可以利用这些信息来进一步攻击系统。
//        禁用 "drop table" 和 "truncate table": 在错误消息中可能包含有关删除表格和数据的信息，攻击者可以利用这些信息来进一步攻击系统。
//        禁用 "union select": 在错误消息中可能包含有关联合查询的信息，攻击者可以利用这些信息来进一步攻击系统。
//    直接代码注入：攻击者通过直接在SQL语句中注入恶意代码来获取对数据库的控制权。这种注入方式通常用于绕过应用程序的过滤器。

//禁用 "exec" 和 "xp_cmdshell"：这些语句可以执行操作系统命令，可能会导致系统被攻击者完全控制。
//
//        禁用 "select @@version" 和 "select database()"：这些语句可以泄漏数据库的版本和名称，为攻击者提供更多的信息。
//
//        禁用 "select user_name()" 和 "select system_user"：这些语句可以泄漏数据库的用户和操作系统的用户，为攻击者提供更多的信息。
//
//        禁用 "waitfor delay" 和 "waitfor time"：这些语句可以让数据库延迟执行，可能会导致服务不可用。
